﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CoreModel.Residency.RoomActivity
{
    /// <summary>
    /// Class, defining time parameters for given activity
    /// </summary>
    public class Reccurance
    {
        /// <summary>
        /// Overriding constructor
        /// </summary>
        /// <param name="strictness"></param>
        /// <param name="type"></param>
        /// <param name="startDateTime"></param>
        public Reccurance(RecStrictness strictness, RecType type, DateTime? startDateTime, IEnumerable<DayOfWeek> daysOfWeek)
        {
            Strictness = strictness;
            Type = type;
            StartDateTime = startDateTime;
            DaysOfWeek = daysOfWeek;
        }

        public const short STRICTNESS_LIMIT = 50;
        public Reccurance()
        {
            Type = RecType.Daily;
            Strictness = RecStrictness.Never;
            StartDateTime = DateTime.MinValue;
            DaysOfWeek = null;
        }

        public IEnumerable<DayOfWeek> DaysOfWeek;

        /// <summary>
        /// Enumerates reccurance type
        /// </summary>
        public enum RecType
        {
            SeveralTimesADay,
            Daily,
            Weekly,
            Monthly,
            Annually
        }

        /// <summary>
        /// Enumeration constants for reccurance strictness
        /// </summary>
        public enum RecStrictness
        {
            Always = 100,
            AlmostAlways = 90,
            MostlyAlways = 80,
            Sometimes = 70,
            Rarely = 50,
            VeryRarely = 25,
            Never = 0
        }

        public RecStrictness Strictness;
        public RecType Type;

        /// <summary>
        /// Date of first occurance
        /// </summary>
        public DateTime? StartDateTime;



        /// <summary>
        /// Returns next occurances of activity. 
        /// If activities strictness is below limit or reccurance is several times a day, then null is return
        /// </summary>
        /// <param name="startDate">Date of expected start</param>
        /// <param name="count">Number of expected occurances</param>
        /// <returns></returns>
        public IEnumerable<DateTime?> GetNextOccurances(DateTime startDate, int count)
        {
            if ((int)Strictness < STRICTNESS_LIMIT || Type == RecType.SeveralTimesADay) yield return null;



            for (int i = 0; i < count; i++)
                if (DaysOfWeek != null && DaysOfWeek.Count() > 0)
                {
                    //we have defined Days of week so we use them
                    yield return EvaluateDOW(startDate, count, DaysOfWeek);
                }
                else
                    switch (Type)
                    {
                        case RecType.Daily:
                            yield return startDate.AddDays(i);
                            break;
                        case RecType.Weekly:
                            yield return startDate.AddDays(i * 7);
                            break;
                        case RecType.Monthly:
                            yield return startDate.AddMonths(i);
                            break;
                        case RecType.Annually:
                            yield return startDate.AddYears(i);
                            break;
                    }

        }

        /// <summary>
        /// Gets next DOW from set date
        /// </summary>
        /// <param name="startDate"></param>
        /// <param name="count"></param>
        /// <param name="daysOfWeek"></param>
        /// <returns></returns>
        private DateTime? EvaluateDOW(DateTime startDate, int count, IEnumerable<DayOfWeek> daysOfWeek)
        {
            int offset = 0;
            int occurance = 0;
            //some sanity check for worst-case scenario
            //if only one DOW is specified
            while (offset < (count + 1) * 7)
            {
                var newday = startDate.AddDays(offset);
                if (daysOfWeek.Contains(newday.DayOfWeek)) occurance++;
                if (occurance == count) return newday;
            }
            return null;
        }


        /// <summary>
        /// Returns next occurances of activity starting from current start date. 
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        public IEnumerable<DateTime?> GetNextOccurances(int count)
        {
            return StartDateTime.HasValue ? GetNextOccurances(StartDateTime.Value, count) : null;
        }
    }
}
